﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Shoozla.DataModel.Helper;
using System.Windows.Media.Imaging;



namespace Shoozla.DataModel
{

    /// <summary>
    /// Add-in to search for tags
    /// </summary>
    public abstract class TagsSearcher : IProgressOperation
    {

        //IProgressOperation
        public event EventHandler<ProgressOperationEventArgs> ProgressChanged;
        public event EventHandler<ProgressOperationEventArgs> Completed;
        public event EventHandler<ProgressOperationEventArgs> Started;
        public int Minimum { get; set; }
        public int Maximum { get; set; } //do not call set
        public int Current { get; set; }
        public String Description { get; set; }

        //public information
        public String AddInName { get; protected set; }
        public String AddInDescription { get; protected set; }

        private List<Mp3File> _externalMp3List;       
        private ParallelOptions _parallelSettings;
        private CancellationTokenSource _cancelTokenSource;
        public CancellationTokenSource CancelTokenSource {
            get { return _cancelTokenSource; } 
            set {
                if (value != _cancelTokenSource && value != null)
                {
                    if (value.Token.IsCancellationRequested)
                        throw new Exception("Cannot set CancelToken");

                    _cancelTokenSource = value;
                    _parallelSettings.CancellationToken = value.Token;
                }
            }
        }   

        //events
        public event TagsFoundHandler TagFound;
        public delegate void TagsFoundHandler ();

        public ThreadingFactorEnum ThreadingFactor { get; set; }
        public List<Mp3File> Songs { get; set; }
        private StatisticsLogger _stats;

        public TagsSearcher()
        {
            SetAddInInformation();

            Completed += new EventHandler<ProgressOperationEventArgs>(FinalizeSearchOnCompleted);
            _parallelSettings = new ParallelOptions();
            ThreadingFactor = ThreadingFactorEnum.Maximum;
        }

        public TagsSearcher(CancellationTokenSource cancelTokenSource, StatisticsLogger stats)
            : this()
        {
            _stats = stats;         
            CancelTokenSource = cancelTokenSource;            
        }

        public override string ToString()
        {
            return AddInName;
        }

        protected virtual void SetAddInInformation()
        {
            AddInName = "Generic Cover Searcher";
            AddInDescription = "Add-in Description";
        }

        public BitmapImage Icon
        {
            get { return ImageHelper.ImageToBitmapImage(GetIcon()); } 
        }

        public virtual Image GetIcon()
        {
            return null;
        }

        /// <summary>
        /// To call before to search
        /// </summary>
        /// <returns>false if the initialization fails</returns>
        public virtual bool Initialize() { return true; }

        public void CancelAsync() { CancelTokenSource.Cancel(); StopSearch(); }

        public void StopSearch()
        {
            Trace.WriteLine("Search stopped");
            RaiseSearchCompleted(Current);
        }

        #region events

        void FinalizeSearchOnCompleted(object sender, ProgressOperationEventArgs e)
        {
            int totalFileProcessed = _externalMp3List.Count;
            int totalFileMatched = Songs.Count<Mp3File>((c) => c.ImageUrl != null);

            _stats.TotalSearchedFiles = totalFileProcessed;
            _stats.SongFound = Songs.Count;

            Trace.WriteLine(String.Format("[INFO] Tag Search Terminated"));
        }

        protected void RaiseProgressMade()
        {
            Current++;

            if (ProgressChanged != null)
                foreach (Delegate d in ProgressChanged.GetInvocationList())
                {
                    d.DynamicInvoke(new object[] { this, new ProgressOperationEventArgs(Current, Maximum, Description) });
                }
        }

        protected void RaiseTagsFound(Mp3File mp3)
        {
            Trace.WriteLine(String.Format("[TAGS] Found {0} - {1} ({2}) ", mp3.Artist, mp3.Title, mp3.AlbumName));

            if (TagFound != null)
                foreach (Delegate d in TagFound.GetInvocationList())
                {
                    d.DynamicInvoke();
                }
        }

        private void RaiseSearchStarted(int count)
        {
            Description = "Searching tags for " + count + " tracks";
            Maximum = count;
            Current = 0;

            if (Started != null)
                foreach (Delegate d in Started.GetInvocationList())
                {
                    d.DynamicInvoke(new object[] { null, new ProgressOperationEventArgs(0, Maximum, Description) });
                }
        }

        private void RaiseSearchCompleted(int found)
        {
            Description = "Search for tags completed";
            if (Completed != null)
                foreach (Delegate d in Completed.GetInvocationList())
                {
                    d.DynamicInvoke(new object[] { this, new ProgressOperationEventArgs(found, Maximum, Description) });
                }
        }

        #endregion

        /// <summary>
        /// Find all the tags for the given Mp3 list, multithread function.
        /// Before to call the Songs property must be initialized with the Mp3 list
        /// Asynchronous call
        /// </summary>
        public void Start()
        {
            if (Songs == null || Songs.Count <= 0)
                throw new ArgumentNullException("Song list is empty");

            if(_parallelSettings.CancellationToken.IsCancellationRequested)
                throw new Exception("Cancel Token already signaled");
            try
            {
                Task.Factory.StartNew((Action)(() => FindMultipleTags()),TaskCreationOptions.LongRunning);
            }
            catch
            {
                StopSearch();
            }

        }

        /// <summary>
        /// Synchronous call
        /// </summary>
        private void FindMultipleTags()
        {       

            _externalMp3List = Songs;
            _parallelSettings.MaxDegreeOfParallelism = ThreadHelper.CalculateTreads(Songs.Count, ThreadingFactor);
            
            RaiseSearchStarted(Songs.Count);

            try
            {
                Parallel.ForEach(Songs, _parallelSettings, m => FindTags(m));
                RaiseSearchCompleted(Current);
            }
            catch (OperationCanceledException ae)
            {
                StopSearch();
            }
            finally {
                Songs = null;
            }            
        }
   
        #region Find methods

        public virtual bool FindTagsInternal(Mp3File song) { return true; }

        /// <summary>
        /// Find the principal tags of a song
        /// </summary>
        /// <returns>true if match found</returns>
        public  bool FindTags(Mp3File song) {
            var res = FindTagsInternal(song);     
      
            if (res)
                RaiseTagsFound(song);

            RaiseProgressMade(); //to track the overall progress
            return res;
        }


        #endregion

    }
}
